/*
 * Copyright 2010 Christoph Widulle (christoph.widulle@googlemail.com)
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the
 * License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS"
 * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */


package com.android1.amarena2d.texture;

import com.android1.amarena2d.commons.Constants;
import com.android1.amarena2d.commons.Size;
import com.android1.amarena2d.engine.EngineObject;
import com.android1.amarena2d.engine.Resource;
import com.badlogic.gdx.graphics.GL10;
import com.badlogic.gdx.graphics.Mesh;
import com.badlogic.gdx.graphics.VertexAttribute;
import com.badlogic.gdx.graphics.VertexAttributes;
import com.badlogic.gdx.math.Vector2;

public class QuadTexturedMesh extends EngineObject implements TexturedMesh, Resource.Consumer {

    protected Manager resourceManager = new Manager(this);

    protected ManagedTexture managedTexture;
    protected TextureFrame textureFrame;
    protected final Size size = new Size();
    protected final Vector2 pos = new Vector2();
    protected final float[] vertics = new float[16];
    protected Mesh mesh;
    protected boolean dirty;
    protected boolean flipX;
    protected boolean flipY;

    public QuadTexturedMesh(final TextureFrame textureFrame) {
        this.managedTexture = textureFrame.getManagedTexture();
        this.textureFrame = textureFrame;
        dirty = true;
    }

    public QuadTexturedMesh(final ManagedTexture managedTexture) {
        this(managedTexture.getTextureFrame());
    }

    public QuadTexturedMesh(final ManagedTexture texture, final int srcX, final int srcY, final int srcWidth, final int srcHeight) {
        this(new TextureFrameImpl(texture, srcX, srcY, srcWidth, srcHeight));
    }

    public void setTextureFrame(TextureFrame textureFrame) {
        final ManagedTexture newTexture = textureFrame.getManagedTexture();
        if (!this.managedTexture.equalResource(newTexture)) {
            this.managedTexture.releaseRef(this);
            newTexture.obtainRef(this);
        }
        this.managedTexture = newTexture;
        this.textureFrame = textureFrame;
        dirty = true;
    }

    public void setSize(final float width, final float height) {
        size.set(width, height);
        dirty = true;
    }

    @Override
    public void setWidth(float width) {
        size.setWidth(width);
        dirty = true;
    }

    @Override
    public void setHeight(float height) {
        size.setHeight(height);
        dirty = true;
    }


    public void render() {

        if (mesh == null) {
            init();
        }
        if (dirty)
            updateMesh();

        managedTexture.bind();
        mesh.render(GL10.GL_TRIANGLE_FAN);
    }

    private void updateMesh() {

        if (size.isZero())
            size.set(textureFrame.getWidth(), textureFrame.getHeight());

        float u = textureFrame.getX() * managedTexture.getInvTexWidth();
        float u2 = (textureFrame.getX() + textureFrame.getWidth()) * managedTexture.getInvTexWidth();

        float v = textureFrame.getY() * managedTexture.getInvTexHeight();
        float v2 = (textureFrame.getY() + textureFrame.getHeight()) * managedTexture.getInvTexHeight();


        final float x2 = pos.x;
        final float x1 = pos.x + size.width;
        final float y2 = pos.y;
        final float y1 = pos.y + size.height;


        if (flipX) {
            float tmp = u;
            u = u2;
            u2 = tmp;
        }

        if (flipY) {
            float tmp = v;
            v = v2;
            v2 = tmp;
        }

        vertics[0] = x2;
        vertics[1] = y2;
        vertics[2] = u;
        vertics[3] = v2;

        vertics[4] = x1;
        vertics[5] = y2;
        vertics[6] = u2;
        vertics[7] = v2;

        vertics[8] = x1;
        vertics[9] = y1;
        vertics[10] = u2;
        vertics[11] = v;

        vertics[12] = x2;
        vertics[13] = y1;
        vertics[14] = u;
        vertics[15] = v;

        mesh.setVertices(vertics);

        dirty = false;

    }

    public void dispose() {
        if (canDispose()) {
            this.managedTexture.releaseRef(this);
            this.managedTexture.dispose();
            mesh.dispose();
            mesh = null;
            resourceManager.dispose();
        }
    }

    @Override
    public void init() {

        if (!isInit()) {

            this.managedTexture.obtainRef(this);
            this.managedTexture.init();

            mesh = new Mesh(true, 4, 4,
                    new VertexAttribute(VertexAttributes.Usage.Position, 2, "a_position"),
                    new VertexAttribute(VertexAttributes.Usage.TextureCoordinates, 2, "a_texCoord"));

            mesh.setIndices(Constants.MESH_DEFAULT_INDICIES);

            updateMesh();

            resourceManager.init();
        }

    }

    public boolean canDispose() {
        return (isInit() && resourceManager.canDispose());
    }

    @Override
    public void obtainRef(Consumer consumer) {
        resourceManager.obtainRef(consumer);
    }

    @Override
    public void releaseRef(Consumer consumer) {
        resourceManager.releaseRef(consumer);
    }

    @Override
    public void releaseAllRefs() {
        resourceManager.releaseAllRefs();
    }

    @Override
    public boolean hasRefs() {
        return resourceManager.hasRefs();
    }

    @Override
    public void register(StateListener listener) {
        resourceManager.register(listener);
    }

    @Override
    public void unregister(StateListener listener) {
        resourceManager.unregister(listener);
    }

    @Override
    public void markPersistent() {
        resourceManager.markPersistent();
    }

    @Override
    public void revokePersistent() {
        resourceManager.revokePersistent();
    }

    @Override
    public boolean isEagerDispose() {
        return resourceManager.isEagerDispose();
    }

    @Override
    public void setEagerDispose(boolean dispose) {
        resourceManager.setEagerDispose(dispose);
    }

    @Override
    public boolean equalResource(Resource otherResource) {
        return this == otherResource;
    }

    @Override
    public boolean isInit() {
        return mesh != null;
    }

    @Override
    public TexturedMesh copy() {
        QuadTexturedMesh texturedMesh = new QuadTexturedMesh(this.managedTexture, textureFrame.getX(), textureFrame.getY(), textureFrame.getWidth(), textureFrame.getHeight());
        texturedMesh.setFlipX(isFlipX());
        texturedMesh.setFlipY(isFlipY());
        texturedMesh.setSize(size.getWidth(), size.getHeight());
        return texturedMesh;
    }

    @Override
    public TextureFrame getTextureFrame() {
        return textureFrame;
    }

    @Override
    public ManagedTexture getManagedTexture() {
        return managedTexture;
    }

    @Override
    public boolean isDirty() {
        return dirty;
    }

    @Override
    public Size getSize() {
        return size;
    }


    @Override
    public float getX() {
        return pos.x;
    }

    @Override
    public float getY() {
        return pos.y;
    }

    @Override
    public void setX(float x) {
        pos.x = x;
        this.dirty = true;
    }

    @Override
    public void setY(float y) {
        pos.y = y;
        this.dirty = true;
    }

    @Override
    public void setXY(float x, float y) {
        pos.x = x;
        pos.y = y;
        this.dirty = true;
    }

    @Override
    public Vector2 getXY() {
        return pos;
    }

    public void setFlipX(boolean flipX) {
        this.flipX = flipX;
        this.dirty = true;
    }

    @Override
    public boolean isFlipX() {
        return flipX;
    }

    @Override
    public boolean isFlipY() {
        return flipY;
    }

    public void setFlipY(boolean flipY) {
        this.flipY = flipY;
        this.dirty = true;
    }

    @Override
    public float getWidth() {
        return size.width;
    }

    @Override
    public float getHeight() {
        return size.height;
    }

    @Override
    public boolean equals(Object obj) {
        return this == obj;
    }
}